﻿//Ben Gotthold
// Joe Illuminati
// CISC 360
// 9/23/2013
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Text.RegularExpressions;

namespace CISC_360_project
{
    public class InstructionParser
    {
        private String[] _assembly_file;
        private List<Instruction> _text;
        private int _current_line;
        private List<int> _used_memory_addresses;
        private Dictionary<String, int> _lookup;
        private static Dictionary<String, byte> _opcode;

        public String[] ASSEMBLY_FILE { get { return _assembly_file; } }
        public List<Instruction> TEXT { get { return _text; } }
        public int CURRENT_INSTRUCTION { get { return _current_line; } }
        public List<int> USED_MEMORY_ADDRESSES { get { return _used_memory_addresses; } }

        /// <summary>
        /// Constructor for the InstructionParser class
        /// </summary>
        public InstructionParser()
        {
            _assembly_file = null;
            _current_line = 0;
            _used_memory_addresses = new List<int>();
            _lookup = new Dictionary<String, int>();
            _opcode = new Dictionary<String, byte>();
            _opcode.Add("add", 0x00);
            _opcode.Add("sub", 0x01);
            _opcode.Add("and", 0x02);
            _opcode.Add("or", 0x03);
            _opcode.Add("mul", 0x04);
            _opcode.Add("div", 0x05);
            _opcode.Add("nota", 0x06);
            _opcode.Add("lda", 0x07);
            _opcode.Add("sta", 0x08);
            _opcode.Add("ba", 0x09);
            _opcode.Add("be", 0x0a);
            _opcode.Add("bl", 0x0b);
            _opcode.Add("bg", 0x0c);
            _opcode.Add("bge", 0x0d);
            _opcode.Add("ble", 0x0e);
            _opcode.Add("bne", 0x0f);
            _opcode.Add("jmp", 0x10);
            _opcode.Add("ret", 0x11);
            _opcode.Add("nop", 0x12);
            _opcode.Add("hlt", 0x13);
            _opcode.Add("set", 0x14);
            _opcode.Add("sethi", 0x15);
        }

        /// <summary>
        /// Creates the binary file and saves it at the inputed locaiton from
        /// the Instructions created from the instruction parser.  Will also fill
        /// the _used_memory_addresses List.
        /// </summary>
        /// <param name="binary_file_loc"></param>
        public void createBinary(String binary_file_loc)
        {
            List<Instruction> instructions = parseAssemblyFile();
            using (BinaryWriter bw = new BinaryWriter(File.Open(binary_file_loc,FileMode.Create)))
            {
                foreach (Instruction instr in instructions)
                {
                    // get the instruction from the Instruction struct
                    String[] line = instr.instruction.Split(new char[] {' '}, StringSplitOptions.RemoveEmptyEntries);
                    // Write the opcode to the binary file
                    // bits 1-8
                    bw.Write((byte)_opcode[line[0]]);
                    // Write the next 3 bytes to the binary file
                    if (line.Length == 1)
                    {
                        if (line[0].Equals("nota"))
                        {
                            // nota a uses the acc register, is an alu
                            // operation, and can't have an immediate value
                            bw.Write((byte)0x60);
                            bw.Write((byte)0x00);
                            bw.Write((byte)0x00);
                        }
                        else if (line[0].Equals("nop"))
                        {
                            // nop uses no register, is not an alu operation
                            // and cannot have an immediate value
                            bw.Write((byte)0x00);
                            bw.Write((byte)0x00);
                            bw.Write((byte)0x00);
                        }
                        else if (line[0].Equals("ret"))
                        {
                            // ret does not use a register and doesn't
                            // use ALU or immediate value
                            bw.Write((byte)0x00);
                            // no label
                            bw.Write((byte)0x00);
                            // return address? ***NEED TO FIGURE OUT HOW TO ADDRESS***
                            bw.Write((byte)0x00);
                        }
                        else if (line[0].Equals("hlt"))
                        {
                            // hlt does not use a register, is not an alu operation
                            // and has no label
                            bw.Write((byte)0x00);
                            bw.Write((byte)0x00);
                            bw.Write((byte)0x00);
                        }
                    }
                    else
                    {
                        // Instruction parser will detect incorrect instructions, so by the time
                        // we make it to this step there won't be a sta with an immediate value
                        // so we don't have to worry about catching the exception.
                        if (line[0].Equals("lda") || line[0].Equals("sta"))
                        {
                            if (line[1].Contains("#"))
                            {
                                // immediate flag
                                bw.Write((byte)0x10);
                                // no label
                                bw.Write((byte)0x00);
                                // immediate value/memory address
                                bw.Write(Convert.ToByte(line[1].Substring(2, line[1].Length - 2)));
                            }
                            else
                            {
                                // no immediate value
                                bw.Write((byte)0x00);
                                // no label
                                bw.Write((byte)0x00);
                                // immediate value/memory address
                                bw.Write(Convert.ToByte(line[1].Substring(1, line[1].Length - 1)));
                                // Put address into used_memory_addresses
                                int mem_adr = Convert.ToInt32(line[1].Substring(1, line[1].Length - 1));
                                if (!_used_memory_addresses.Contains(mem_adr))
                                {
                                    _used_memory_addresses.Add(mem_adr);
                                }
                            }
                        }
                        // Branching operations
                        else if (line[0].StartsWith("b") || line[0].Equals("jmp"))
                        {
                            // branches don't use a register and doesn't
                            // use ALU or an immediate value
                            bw.Write((byte)0x00);
                            // next byte will be label
                            bw.Write(Convert.ToByte(line[1].Substring(0, line[1].Length)));
                            // no memory address/ immediate value
                            bw.Write((byte)0x00);
                        }
                        // set and sethi operations
                        else if (line[0].Equals("set") || line[0].Equals("sethi"))
                        {
                            // has to be an immediate value
                            bw.Write((byte)0x30);
                            String value = line[1].Substring(2, line[1].Length - 2);
                            ushort immediate = Convert.ToUInt16(value);
                            byte[] bytes = BitConverter.GetBytes(immediate);
                            bw.Write(bytes[1]);
                            bw.Write(bytes[0]);
                        }
                        // All ALU operations
                        else
                        {
                            if (line[1].Contains("#"))
                            {
                                // immediate flag
                                bw.Write((byte)0x30);
                                // no label
                                bw.Write((byte)0x00);
                                // immediate value/memory address
                                bw.Write(Convert.ToByte(line[1].Substring(2, line[1].Length - 2)));
                            }
                            else
                            {
                                // no immediate value
                                bw.Write((byte)0x20);
                                // no label
                                bw.Write((byte)0x00);
                                // immediate value/memory address
                                bw.Write(Convert.ToByte(line[1].Substring(1, line[1].Length - 1)));
                                int mem_adr = Convert.ToInt32(line[1].Substring(1, line[1].Length - 1));
                                if (!_used_memory_addresses.Contains(mem_adr))
                                {
                                    _used_memory_addresses.Add(mem_adr);
                                }
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Prompts the user to load a .s file from their machine.
        ///  Will throw an exception if file load not successful.
        /// </summary>
        public void loadAssemblyFile(String filename) 
        {
            try
            {
                // Write the contents of the file to _assembly file
                _assembly_file = File.ReadAllLines(filename);
                _current_line = 0;
                _lookup.Clear();
                _used_memory_addresses.Clear();
            }
            catch (Exception err)
            {
                throw err;
            }
        }

        /// <summary>
        /// Parses the assembly file and returns a list of Instructions
        /// </summary>
        /// <returns></returns>  
        public List<Instruction> parseAssemblyFile()
        {
            
            if (_assembly_file == null)
            {
                return null;
            }

            List<Instruction> instructions = new List<Instruction>();
            
            List<String> line = null;
            String label = null;
            int instruction_num = 0;
            Regex regex = new Regex(@"\S+");
            
            do
            {
                line = new List<String>();
                //Split the line into an array of each element of the string, ignoring spaces
                MatchCollection results = regex.Matches(currentLine(),0);
                if (results.Count != 0)
                {
                    foreach (Match match in results)
                    {
                        line.Add(match.ToString().ToLower());
                    }

                    Instruction instr = new Instruction();

                    if (line[0].Contains(":") && line.Count == 1)
                    {
                        _lookup.Add(line[0].TrimEnd(':'), instruction_num);
                        label = line[0];
                    }
                    else if (line.Count == 1)
                    {
                        instr.instruction = line[0];
                        instr.label = null;
                        instr.instruction_num = instruction_num;
                        instruction_num++;
                    }
                    else if (line.Count != 1)
                    {
                        if (line[0].Contains(":"))
                        {
                            _lookup.Add(line[0].TrimEnd(':'), instruction_num);
                            if (line[1].StartsWith("b") || line[1].Equals("jmp"))
                            {
                                instr.instruction = line[1];
                                instr.instruction_num = instruction_num;
                                instruction_num++;
                                instr = splitInstructionAndComment(instr, line, 2, false);
                            }
                            else
                            {
                                instr.label = null;
                                instr.instruction_num = instruction_num;
                                instruction_num++;
                                instr = splitInstructionAndComment(instr, line, 1);
                            }
                        }
                        else
                        {
                            if (line[0].StartsWith("b") || line[0].Equals("jmp"))
                            {
                                instr.instruction = line[0];
                                instr.instruction_num = instruction_num;
                                instruction_num++;
                                instr = splitInstructionAndComment(instr, line, 1, false);
                            }
                            else
                            {
                                instr.label = null;
                                instr.instruction_num = instruction_num;
                                instruction_num++;
                                instr = splitInstructionAndComment(instr, line, 0);
                            }
                        }
                    }
                    if (instr.instruction != null && isValidInstruction(instr)) 
                    {
                        instructions.Add(instr);
                    }
                }
            } while (incrementCurrentLine());

            // read through each instruction and adjust the 
            // label_address field if the instruction contains a label
            for (int i = 0; i < instructions.Count; i++ )
            {
                instructions[i] = addLabelAddresses(instructions[i]);
            }
            _text = instructions;
            return instructions;
        }

        /// <summary>
        /// Reads through the list of Instructions and updates the value of label_address
        /// based on the _lookup dictionary.
        /// </summary>
        /// <param name="instructions"></param>
        private Instruction addLabelAddresses(Instruction instruction)
        {
            if (instruction.label != null)
            {
                try
                {
                    instruction.label_address = _lookup[instruction.label];
                    instruction.instruction += " " + instruction.label_address;
                }
                catch (Exception)
                {
                    throw new Exception("LABEL DOES NOT EXIST AT LINE " + instruction.instruction_num);
                }
            }
            return instruction;
        }

        /// <summary>
        /// Increments the current line counter by one until it reaches the last instruction.  Any
        /// calls made to this method after the last instruction is reached won't increment the 
        /// current line counter.
        /// </summary>
        public Boolean incrementCurrentLine()
        {
            if (_current_line < _assembly_file.Length - 1)
            {
                _current_line++;
                return true;
            }
            return false;
        }

        /// <summary>
        /// Retuns a String of the current line from the Assembly File
        /// </summary>
        /// <returns>
        /// String of current instruction
        /// </returns>
        public String currentLine()
        {
            if (_assembly_file != null)
            {
                return _assembly_file[_current_line];
            }
            else
            {
                return "No file loaded.";
            }
        }

        /// <summary>
        /// Splits up an instruction into it's instruction part and comment part.  If there is no comment, then
        /// it is all dumped into the instruction field of Instruction.
        /// </summary>
        /// <param name="i"></param>
        /// <param name="line"></param>
        /// <param name="start_index"></param>
        private Instruction splitInstructionAndComment(Instruction i, List<String> line, int start_index, bool put_in_instruction = true)
        {
            Instruction result = i;
            bool hit_exp = false;
            for (int a = start_index; a < line.Count; a++ )
            {
                if (line[a].Equals("!"))
                {
                    hit_exp = true;
                }
                else
                {
                    if (!hit_exp && put_in_instruction)
                    {
                        result.instruction += line[a] + " ";
                    }
                    else if (!hit_exp && !put_in_instruction)
                    {
                        if (a == start_index)
                        {
                            result.label += line[a];
                        }
                        else
                        {
                            result.label += " " + line[a];
                        }
                    }
                    else
                    {
                        result.comment += line[a] + " ";
                    }
                }
            }
            return result;
        }

        /// <summary>
        /// Checks to see if an instruction is a valid instruction.  If it is not
        /// it will throw an exception depending on the error type.
        /// </summary>
        /// <param name="instruction"></param>
        /// <returns></returns>
        private Boolean isValidInstruction(Instruction instruction)
        {
            String[] valid_instructions = { "lda", "sta", "add", "sub", "and", "or", "mul", "div","nota", "ba", "be", "bl", "bg", "bge", "ble", "bne", "nop", "jmp", "ret", "hlt", "set", "sethi" };
            Regex regex = new Regex(@"\S+");
            List<String> instr = new List<String>();
            foreach (Match match in regex.Matches(instruction.instruction))
            {
                instr.Add(match.ToString());
            }

            // Check if valid operand type
            Boolean valid_instruction = false;
            foreach (String s in valid_instructions)
            {
                if(instr[0].Equals(s))
                {
                    valid_instruction = true;
                }
            }
            if (!valid_instruction)
            {
                throw new Exception("INVALID OPERAND AT LINE " + instruction.instruction_num);
            }

            // Check that instructions don't contain too many arguments
            else if (instr[0].Equals("nop") || instr[0].Equals("nota") || instr[0].Equals("ret") || instr[0].Equals("hlt"))
            {
                if (instr.Count > 1)
                {
                    throw new Exception(instr[0].ToUpper() + " DOES NOT HAVE ANY ARGUMENTS.");
                }
            }
            else if (instr[0].StartsWith("b") || instr[0].Equals("jmp"))
            {
                // label is stored in Instruction.label, so the length of Instruction.instruction
                // should only be 1.
                if (instr.Count > 1)
                {
                    throw new Exception(instr[0].ToUpper() + " SHOULD ONLY HAVE A LABEL AS AN ARGUMENT");
                }
            }
            else
            {
                // all arithmetic operations and load and store should have to strings.
                if (instr.Count > 2)
                {
                    throw new Exception(instr[0].ToUpper() + " SHOULD ONLY HAVE ONE ARGUMENT");
                }
            }

            // Check to make sure arithemtic and load/store operations contain 
            // correct number of arguments and that the arguments are defined 
            // properly (starting with $ or #$)
            if (instr[0].Equals("lda") || instr[0].Equals("sta") || instr[0].Equals("add") || instr[0].Equals("sub") || instr[0].Equals("and") || instr[0].Equals("or") || instr[0].Equals("mul") || instr[0].Equals("div"))
            {
                if (instr.Count != 2)
                {
                    throw new Exception(instr[0].ToUpper() + " IS MISSING AN ARGUMENT");
                }
                else if (!instr[1].StartsWith("$") && !instr[1].StartsWith("#$"))
                {
                    throw new Exception("ARGUMENT FOR " + instr[0].ToUpper() + " IS NOT DEFINED CORRECTLY");
                }
            }

            // Check to make sure we aren't storing an immediate value
            if (instr[0].Equals("sta"))
            {
                if (instr[1].StartsWith("#$"))
                {
                    throw new Exception("CANNOT STORE AN IMMEDIATE VALUE AT LINE " + instruction.instruction_num);
                }
            }

            // Check to make sure we are only setting an immediate value
            if (instr[0].Equals("set") || instr[0].Equals("sethi"))
            {
                if (!instr[1].StartsWith("#$"))
                {
                    throw new Exception("CAN ONLY SET AN IMMEDIATE VALUE AT LINE " + instruction.instruction_num);
                }
            }

            // If we made it this far, then it's a valid instruction!
            return true;
        }

    }
}
